# antd vue 实现手动上传功能
antd 的上传组件upload是通过action直接触发上传到服务器,点击上传文件就向服务器上传一次文件,multiple属性也只是支持在选择时选择多个文件,选择好也是直接上传,如果我们想要实现选择好文件后再手动提交文件,并在提交之前可以对文件进行删除等操作该如何实现呢?
要实现手动上传,我们可以借助upload组件提供的customRequest
属性实现自己的上传逻辑。
比如我们在antd pro of vue 中实现一个表单提交带有相关图片的功能
1、此表单带有编辑和添加功能,在编辑时,获取到已经有的图片列表,给定一个类型和新添加的做一个区别,这样我们在提交的时候根据这个类型让后端定义不同的参数来进行提交。
2、照片墙功能我们把组件自带的文件列表给隐藏,然后自定义一个图片列表来展示已经上传的图片,在上面定义一些预览和删除操作来控制我们的照片墙。
3、上传前,通过beforeUpload
方法来限制上传图片的类型格式、大小和相关提示。
4、然后通过自定义方法customRequest
的参数获取到文件内容file,把获取到的内容渲染到照片墙上,有的插件会返回图片的base64地址,但antd没有返回,我们可以使用FileReader
把图片转为base64地址。
5、获取到内容push到文件列表中,然后手动提交表单,使用formData() 把文件传入到服务器上。
6、我们可以使用数据请求api自带的onUploadProgress
属性来对接口提交进度进行监听,从而实现上传百分比进度条。
同理其他上传功能也是可以类似处理,如果要想每个文件都实现用进度条来展示,要么改变业务需求,直接使用组件默认的功能,使用action直接上传,这样每个图片都有自己的进度条,还可以根据属性自定义上传时的样式和效果,如果非要一起上传并且每个文件都有进度条,因为接口调一次,只能有一次进度,所以就必须在点击提交时使用Promise
同时多次调用接口,一个接口上传一个文件,这样每个文件都有进度条了,其实这样还不如直接使用默认的上传好。
<template>
<a-modal title="提交相关信息" :visible="true" width="1000px" @cancel="closeDialog">
<a-form-model ref="editForm" :model="formData" :rules="formRule" :label-col="{span: 3}" :wrapper-col="{span:18}">
<a-form-model-item label="名称" prop="name">
<a-input v-model="formData.hot" placeholder="请输入名称"></a-input>
</a-form-model-item>
<a-form-model-item label="相关图片">
<div class="relate-image">
<div class="image-list">
<!-- 上传的图片列表 -->
<div class="list-item" v-for="(item, index) in uploadImg.showImages" :key="item.url">
<img v-if="item.type=='origin'" :src="baseUrl+item.url" alt="相关图片">
<img v-else :src="item.url" alt="相关图片">
<!-- 预览 -->
<div class="item-hover">
<a-icon style="margin-right: 10px;" type="eye" @click="openPreview(index)" />
<a-icon type="delete" @click="deleteRelateImg(index)" />
</div>
</div>
<!-- 上传组件,图片多于5张隐藏 -->
<a-upload
style="width: 104px;"
v-if="uploadImg.showImages && uploadImg.showImages.length < 5"
action="#"
list-type="picture-card"
accept=".jpg, .png"
:show-upload-list="false"
:before-upload="beforeUpload"
:customRequest="customUpload"
>
<a-icon type="plus" />
<div class="ant-upload-text">上传</div>
</a-upload>
</div>
<!-- 上传进度 -->
<a-progress v-if="btnLoading" :percent="uploadProgress" />
<a-modal :visible="uploadImg.previewVisible" :footer="null" @cancel="closePreview">
<img alt="example" style="width: 100%" :src="uploadImg.previewImage" />
</a-modal>
</div>
</a-form-model-item>
</a-form-model>
<template slot="footer">
<a-button @click="closeDialog">取消</a-button>
<a-button type="primary" :loading="btnLoading" @click="editConfirm">确定</a-button>
</template>
</a-modal>
</template>
<script>
import { saveApi } from '@/api/save.js'
export default {
name: 'SaveFile',
props: {
editData: {
type: Object,
default () {
return {}
}
},
},
data() {
return {
baseUrl: process.env.VUE_APP_API_BASE_URL,
btnLoading: false,
formRule: {
name: [
{ required: true, message: '请输入名称', trigger: 'blur' }
],
},
// 表单数据
formData: {},
uploadProgress: 0, // 上传进度
// 图片上传
uploadImg: {
showImages: [], // 相关图片
previewVisible: false,
previewImage: ''
},
}
},
created() {
this.getFromData();
},
methods: {
// 获取表单数据
getFromData() {
if (this.editData.type == 'edit') {
this.formData = this.editData.data;
} else {
this.formData = {
id: 0,
name: '',
images: [],
}
}
// 表单显示已经有的图片
// 这个根据自己情况判断区分
let showImages = [];
this.formData.images.forEach(item => {
let ob = {
type: 'origin',
url: item
}
showImages.push(ob);
});
this.uploadImg.showImages = showImages;
},
// 图片上传前限制
beforeUpload(file) {
const { type, size } = file;
const limitType = type === 'image/jpeg' || type === 'image/png';
if (!limitType) {
this.$message.error('请上传 JPG、PNG 格式图片!');
}
const limitSize = size / 1024 / 1024 < 2;
if (!limitSize) {
this.$message.error('图片不可大于 2MB!');
}
return limitType && limitSize;
},
// 自定义上传获取文件内容
async customUpload(fileInfo) {
const { file } = fileInfo;
try {
const url = await this.fileToBase64(file); // 获取base64地址
let ob = {
type: 'upload',
url,
file
}
this.uploadImg.showImages.push(ob);
} catch (err) {
this.$message.error(err.message);
}
},
// 转换图片地址
fileToBase64(file) {
const reader = new FileReader();
reader.readAsDataURL(file);
return new Promise((resolve, reject) => {
reader.onload = function (e) {
if (this.result) {
resolve(this.result)
} else {
reject("图片转换错误,请稍后重试")
}
}
})
},
// 照片墙预览
openPreview(index) {
// 服务器返回的拼接地址,自己上传的直接显示base64地址,这个根据自己后端返回的图片路径判断
if (this.uploadImg.showImages[index].type === 'upload') {
this.uploadImg.previewImage = this.uploadImg.showImages[index].url;
} else {
this.uploadImg.previewImage = this.baseUrl + this.uploadImg.showImages[index].url;
}
this.uploadImg.previewVisible = true;
},
closePreview() {
this.uploadImg.previewVisible = false;
},
// 删除上传图片
deleteRelateImg(index) {
let that = this;
this.$confirm({
title: `确定删除该图片?`,
okType: 'danger',
onOk() {
that.uploadImg.showImages.splice(index, 1);
},
});
},
// 提交表单
editConfirm() {
this.$refs.editForm.validate(valid => {
if (valid) {
// 使用formData格式提交
const formData = this.formatData();
this.btnLoading = true;
// 自定义的请求,根据自己封装的axios来
saveApi(formData, {
//获取文件上传的进度
onUploadProgress: function (e) {
var completeProgress = ((e.loaded / e.total * 100) | 0);
that.uploadProgress = completeProgress;
}
})
.then(res => {
this.btnLoading = false;
if (res.code != 200) {
this.$notification.error({
message: '错误',
description: res.msg
})
return false
}
this.$message.success('提交成功!');
})
.catch(err => {
this.btnLoading = false;
this.uploadProgress = 0;
this.$notification.error({
message: '错误',
description: err.response.data.msg
})
})
} else {
this.$message.warning("表单填写不完整");
}
});
},
// 转换formData格式
formatData() {
// 文件内容使用formData格式提交,formData格式无法直接打印查看,
// 如果内容为数组则需要通过for循环一个一个append进去
// 如果内容是json对象,则需要使用JSON.stringify(obj)再提交
const { id, name } = this.formData;
let formData = new FormData();
formData.set('id', id);
formData.set('name', title);
// 区分原有的图片,和自己上传的图片,参数和后端定义好
let originImages = this.uploadImg.showImages.filter(item => item.type == 'origin');
if (originImages && originImages.length) {
originImages.forEach(item => {
formData.append('attach[]', item.url)
})
}
let uploadImages = this.uploadImg.showImages.filter(item => item.type == 'upload');
if (uploadImages && uploadImages.length) {
uploadImages.forEach(item => {
formData.append('file[]', item.file)
})
}
return formData
},
closeDialog() {
this.$emit('CLOSE_DIALOG_EVENT');
},
}
}
</script>
<style lang="less" scoped>
.relate-image {
display: flex;
width: 100%;
.image-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.list-item {
position: relative;
height: 104px;
margin: 0 8px 8px 0;
padding: 8px;
border: 1px solid #D9D9D9;
border-radius: 4px;
img {
min-width: 50px;
height: 100%;
}
.item-hover {
position: absolute;
top: 50%;
left: 50%;
z-index: 9;
display: none;
justify-content: center;
align-items: center;
width: calc(100% - 16px);
height: 88px;
color: #fff;
background: rgba(0, 0, 0, .2);
transform: translate(-50%, -50%);
cursor: pointer;
}
&:hover .item-hover {
display: flex;
}
}
}
}
</style>